<?php
/**
 * Zym Framework Demo
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @copyright  Copyright (c) 2008 Zym. (http://www.zym-project.com/)
 * @license http://www.zym-project.com/license New BSD License
 */

/**
 * @see Zend_Db_Table_Abstract
 */
require_once 'Zend/Db/Table/Abstract.php';

/**
 * @author Geoffrey Tran
 * @license http://www.zym-project.com/license New BSD License
 * @copyright  Copyright (c) 2008 Zym. (http://www.zym-project.com/)
 */
class Logs extends Zend_Db_Table_Abstract
{
    /**
     * Import datasource
     *
     * @var string
     */
    const IMPORT_SOURCE = '';

    /**
     * Name
     *
     * @var string
     */
    protected $_name = 'logs';

    /**
     * Search index path
     *
     * @var string
     */
    private $_indexPath;

    /**
     * Search index
     *
     * @var Zend_Search_Lucene
     */
    private $_searchIndex;

    /**
     * Import data
     *
     * @return void
     */
    public function import($channel)
    {
        set_time_limit(100);

        $client = new Zend_Http_Client(self::IMPORT_SOURCE);
        $client->setConfig(array('timeout' => 10));
        $client->setParameterGet(array(
            'mode'    => 'newest',
            'channel' => $channel,
            'store'   => 1 //(Zym_App::getInstance()->getEnvironment() == Zym_App::ENV_PRODUCTION) ? 1 : 0 // Only store when in production
        ));

        $request = $client->request();
        if ($request->isError()) {
            throw new Exception(sprintf('Importer failed due to remote server error "%s"', $request->getStatus()));
        }

        $xml  = simplexml_load_string(iconv('ISO-8859-1', 'UTF-8', $request->getBody()));
        $stmt = $this->getAdapter()->prepare(sprintf(
            'INSERT INTO %s (timestamp, channel, nick, message) VALUES (?, ?, ?, ?)', $this->_name));
        foreach ($xml as $entry) {
            $timestamp = new Zend_Date();
            $timestamp->setTimezone($entry->timestamp['timezone']);
            $timestamp->setTimestamp((int) $entry->timestamp);

            $stmt->execute(array(
                $timestamp->toString('YYYY-MM-ddTHH:mm:ssZZZZ'),
                $channel,
                $entry->nickname,
                $entry->message
            ));
        }

        $stmt->fetch();
    }

    /**
     * Get channel list
     *
     * @return array
     */
    public function getChannels()
    {
        $select = $this->select()
                       ->distinct()
                       ->from($this, array('channel'));

        try {
            return (array) $this->getAdapter()->fetchCol($select);
        } catch (Zend_Db_Exception $e) {
            throw new Exception(sprintf('Could not retrieve channel list: %s', $e->getMessage()));
        }
    }

    /**
     * Get dates list of logs for a channel
     *
     * @todo Improve the query
     * @param string $channel
     * @return array
     */
    public function getDates($channel)
    {
        $select = $this->select()
                       ->distinct()
                       ->from($this, array(new Zend_Db_Expr('DATE(timestamp)')))
                       ->where('channel = ?', $channel);

       // Don't Ask >:(
       try {
            $dates  = $this->getAdapter()->fetchCol($select);

            // $dates is an array of YYYY-MM-DD, so we split it into an array
            $return = array();
            foreach ($dates as $entry) {
            	list($year, $month, $day) = explode('-', $entry);
            	$return[$year][$month][] = $day;
            }

            return $return;
        } catch (Zend_Db_Exception $e) {
            throw new Exception(sprintf('Could not retrieve dates list: %s', $e->getMessage()));
        }
    }

    /**
     * Get messages by day
     *
     * @param string  $channel
     * @param integer $year
     * @param integer $month
     * @param integer $day
     * @return Zend_Paginator
     */
    public function getMessagesByDay($channel, $year, $month, $day)
    {
        $select = $this->select()
                       ->from($this, array('id', 'timestamp', 'nick', 'message'))
                       ->where('channel = ?', $channel)
                       ->where('DATE(timestamp) = ?', "$year-$month-$day");

        $paginator = Zend_Paginator::factory($select);
        $paginator->setPageRange(5)
                  ->setItemCountPerPage(300);

        return $paginator;
    }

    /**
     * Get all messages by day
     *
     * Non paginated
     *
     * @param string  $channel
     * @param integer $year
     * @param integer $month
     * @param integer $day
     * @return array
     */
    public function getAllMessagesByDay($channel, $year, $month, $day)
    {
        $select = $this->select()
                       ->from($this, array('id', 'timestamp', 'nick', 'message'))
                       ->where('channel = ?', $channel)
                       ->where('DATE(timestamp) = ?', "$year-$month-$day");

        $db = $this->getAdapter();
        return $db->fetchAssoc($select);
    }

    /**
     * Get search index path
     *
     * @return string
     */
    public function getIndexPath()
    {
        if ($this->_indexPath === null) {
            $this->_indexPath = Zym_App::getInstance()->getPath(Zym_App::PATH_DATA, 'index/logs');
        }

        return $this->_indexPath;
    }

    /**
     * Set search index path
     *
     * @param string $path
     * @return Logs
     */
    public function setIndexPath($path)
    {
        $this->_indexPath = $path;

        return $this;
    }

    /**
     * Create/Open search index
     *
     * @return Zend_Search_Lucene
     */
    public function getSearchIndex()
    {
        if (!$this->_searchIndex instanceof Zend_Search_Lucene) {
            $indexPath = $this->getIndexPath();
            if (!file_exists($indexPath)) {
                $this->_searchIndex = Zend_Search_Lucene::create($indexPath);
            } else {
                $this->_searchIndex = Zend_Search_Lucene::open($indexPath);
            }
        }

        return $this->_searchIndex;
    }

    /**
     * Delete search index
     *
     * @return Logs
     */
    public function removeSearchIndex()
    {
        $indexPath = $this->getIndexPath();
        if (file_exists($indexPath)) {
            //unlink($indexPath);
        }

        return $this;
    }

    /**
     * Search
     *
     * @param mixed $query
     * @return Zend_Search_Lucene_QueryHit
     */
    public function search($queryString)
    {
        //Zend_Search_Lucene::setResultSetLimit(100);
        //$cache = Zym_Cache::factory();
        //if (!$hits = $cache->load('logs_search_' . md5($queryString))) {
            $index = $this->getSearchIndex();
            $query = Zend_Search_Lucene_Search_QueryParser::parse($queryString);
            $hits  = $index->find($query);

          //  $cache->save($hits);
       // }

        $paginator = Zend_Paginator::factory($hits);

        return $paginator;
    }

    public function addSearchDay($channel, $date, array $logs)
    {
        $logs = implode("\n", $logs);

        $document = new Zend_Search_Lucene_Document();
        $document->addField(Zend_Search_Lucene_Field::Keyword('date', $date))
                 ->addField(Zend_Search_Lucene_Field::Keyword('channel', $channel))
                 ->addField(Zend_Search_Lucene_Field::Text('contents', $logs));

        $index = $this->getSearchIndex();
        $index->addDocument($document);

        return $this;
    }

    /**
     * Set search dat
     *
     * @param string $channel
     * @param string $date
     * @param array $logs
     * @return Logs
     */
    public function setSearchDay($channel, $date, array $logs)
    {
        $dateTerm  = new Zend_Search_Lucene_Index_Term($date, 'date');
        $dateQuery = new Zend_Search_Lucene_Search_Query_Term($dateTerm);

        $channelTerm  = new Zend_Search_Lucene_Index_Term($channel, 'channel');
        $channelQuery = new Zend_Search_Lucene_Search_Query_Term($channelTerm);

        $query = new Zend_Search_Lucene_Search_Query_Boolean();
        $query->addSubquery($dateQuery, true);
        $query->addSubquery($channelQuery, true);

        $index = $this->getSearchIndex();
        $hits  = $index->find($query);

        foreach ($hits as $hit) {
        	$index->delete($hit->id);
        }

        $this->addSearchDay($channel, $date, $logs);

        return $this;
    }

    /**
     * Create the search index
     *
     * @todo Yes I know it's *VERY* inefficient, but it's late and I'm feelin lazy ;)
     *
     * @return Logs
     */
    public function createSearchIndex()
    {
        set_time_limit(100);
        $this->removeSearchIndex();

        $channels = $this->getChannels();
        foreach ($channels as $channel) {
        	$dates = $this->getDates($channel);

        	foreach ($dates as $year => $months) {

        	    foreach ($months as $month => $days) {

        	        foreach ($days as $day) {
                        $logs = $this->getAllMessagesByDay($channel, $year, $month, $day);
                        $logs = $this->_logsToIndexableContent($logs);

                        $this->addSearchDay($channel, "$year-$month-$day", $logs);
        	        }
        	    }
        	}
        }

        return $this;
    }

    /**
     * Convert array of log rows into indexable content field
     *
     * @param array $logs
     * @return array
     */
    private function _logsToIndexableContent(array $logs)
    {
        $return = array();
        foreach ($logs as $row) {
        	$return[] = $row['nick'] . ': ' . $row['message'];
        }

        return $return;
    }
}